/*
This is SHAID, the Survival Horror Artificial Intelligence Director
made by Wunce 

Yes, I learnt how to make timers whilst making this and I must say they are pretty cool

This is going to be released first I think, as many people would prefer something togleable over the more permanent limb_health
*/



 CreateConVar( "SHAID_Zombies_Only",  0, { FCVAR_REPLICATED, FCVAR_ARCHIVE }) -- These are working now :D
 CreateConVar( "SHAID_Antlions_Only", 0, { FCVAR_REPLICATED, FCVAR_ARCHIVE })
 CreateConVar( "SHAID_NPC_volume", 10, { FCVAR_REPLICATED, FCVAR_ARCHIVE })

Spawn = {}
NPCenemies = {}
totalenemies = 0
i = 0
slaughter = 0
enpersec = 0
zombieamt = 11
antlionamt = 9
remainder = 0
npctotalnum = 0
desiredslaughter = 1
enemyhp = 1

timer.Create("Director", 10 , 0 , function()
if SHAID == true then
slaughter = (4 * slaughter) / 5 --slaughter is constantly decreasing so previous kills weigh less than recent ones
count = 1
local totalhealth = 0
local totalleg = 0		-- ammo should probably be here also
local totalarm = 0
local totalhead = 0
enpersec = 0

// Data Collection //
for k, ply in pairs (player.GetAll()) do
if ply.optin == true then --so that players who don't want to kill zombies aren't chased
ply.npctargetnum = count -- this could probably be replaced with "k"
count = count + 1
totalhealth = ply:Health() + totalhealth

if ply.hitgroups[HITGROUP_LEFTLEG] != nil then
totalleg = totalleg + ply.hitgroups[HITGROUP_LEFTLEG] + ply.hitgroups[HITGROUP_RIGHTLEG] 
totalarm = totalarm + ply.hitgroups[HITGROUP_LEFTARM] + ply.hitgroups[HITGROUP_RIGHTARM]
totalhead = totalhead + ply.hitgroups[HITGROUP_HEAD]
end

slaughter = ply.killsection + slaughter
ply.killsection = 0
end
end

// Data Interpretation //
	if totalleg != nil then
		if totalleg/(count-1) > 40 then
		antlionamt = 1
		elseif totalleg/(count-1) < 15 then
		antlionamt = 9
		else
		antlionamt = 5
		end

		if totalarm/(count-1) > 40 then
		zombieamt = 19
		elseif totalarm/(count-1) < 15 then
		zombieamt = 11
		else
		zombieamt = 15
		end

		if totalhead/(count-1) < 5 then
		hcrab = false
		else
		hcrab = true
		end
	end

if totalhealth/(count-1) > 80 then 	
enpersec = 0.1
elseif totalhealth/(count-1) < 40 then
enpersec = -0.1
else 
enpersec = 0
end

/* FOR THE SLAUGHTER SECTION
its the sum to infinity of "x" (the number of kills each 10 sec, assume constant) and the ratio is (4/5)
this produces the equation 5x. In short that means that 5 multiplied by the kills per 10 sec is the slaughter figure,
so to work out the enemies to produce per sec, the desired slaughter is divided by 5 then 10 (50).
*/
local pi = 3.141592654	
-- IF YOU DO NOT UNDERSTAND THE GRAPH OF TRIGONOMETRIC FUNCTIONS DO NOT ALTER THE EQUATION BELOW

// current equation: lasts 20 min; peak of about 18; troff of about 2;
local middlenumber = GetConVarNumber("SHAID_NPC_volume")
if middlenumber < 10 then middlenumber = 10 end
desiredslaughter = middlenumber-7*math.cos((pi* (CurTime() - BootTime))/600)+1.4*math.sin((pi* (CurTime() - BootTime))/150) --surprised to see this works nicely.


if (desiredslaughter + 6) < slaughter then 
bossspawn = true
end

if  slaughter < (desiredslaughter - 4) then
enemyhp = 0.6 --slaughter now determines enemy health, not amount. in otherwords, if you can't cope with the # of enemies, they will spawn weaker
elseif slaughter < (desiredslaughter - 2) then
enemyhp = 0.8
elseif slaughter < (desiredslaughter + 2) then
enemyhp = 1.25
elseif  slaughter > (desiredslaughter + 4) then
enemyhp = 1.66 
else
enemyhp = 1

end
enpersec = desiredslaughter/50 + enpersec --this determines the output of enemies per second. usually will be a fraction.
end
end)


timer.Create("SHAID_Spawn", 1 , 0 , function() --I made this so that it can only spawn 1 enemy at a time.
if SHAID == true and i != 0 and npctotalnum < (math.ceil(desiredslaughter)) then -- /\/\/\/\/ MAX NPCS ASSIGNED ON THIS LINE! /\/\/\/\/\/\/
remainder = remainder + enpersec
if remainder > 1 then
remainder = remainder -1


local EnemyType = DecideEnemyType() --this is where alot of crucial director stuff happens

NPCenemies[totalenemies] = ents.Create(EnemyType) 
	if EnemyType == "npc_metropolice" then --this doesn't look as good as I first thought :O
		NPCenemies[totalenemies]:Give("weapon_stunstick") 
		NPCenemies[totalenemies]:SetHealth(120) -- he's one crazy fucker
	end
	
	local randply = 1
if count > 1 then
	randply = math.random(1, count-1) --this selects a random player to stalk.
end

if OVERRIDE then
	NPCenemies[totalenemies].playertarget = OVERRIDE
else
	for k,ply in pairs(player.GetAll()) do
		if randply == ply.npctargetnum then --FOR FUCKS SAKE THAT WAS THE PROBLEM ALL ALONG? OMG RARGH
			NPCenemies[totalenemies].playertarget = ply
		end
	end
end
local SpawnPoint = LocationSpawner(NPCenemies[totalenemies].playertarget)

NPCenemies[totalenemies]:SetPos(SpawnPoint) 
NPCenemies[totalenemies]:Spawn() 
NPCenemies[totalenemies]:SetHealth(enemyhp * NPCenemies[totalenemies]:GetMaxHealth()) -- it isn't very noticable but its still there
StopTheViolence(NPCenemies[totalenemies]) -- Antlions and zombies now team up... against you
NPCenemies[totalenemies].stupidloop = 29


totalenemies = totalenemies + 1
npctotalnum = npctotalnum + 1
end
end
end)


timer.Create("SHAID_Chase", 0.1, 0, function() -- client keeps timing out. I wonder why. hasn't for a while. still don't know why

for k, NPC in pairs(NPCenemies) do
if NPC.playertarget != nil then
local a = NPC.playertarget:GetPos()
local b = NPC:GetPos()
local c = a-b
local mag = math.sqrt((c.x * c.x) + (c.y * c.y) + (c.z * c.z))
if mag > 1000 and NPC.stupidloop == 30 then
NPC:SetLastPosition(a) -- investigate this tomorrow. WITH A VENGANCE. <<<fixed>>>
NPC:SetSchedule(SCHED_FORCED_GO_RUN)
NPC.stupidloop = 0
elseif mag < 1000 then
if NPC.stupidloop > 1 then
NPC:SetLastPosition(b+(.1*(a-b))) --this makes them stop but not turn around, ie good enough
NPC:SetSchedule(SCHED_FORCED_GO_RUN)
end
NPC:SetEnemy(NPC.playertarget) -- can't figure out how to end a schedule :{
NPC.stupidloop = 0
else
NPC.stupidloop = NPC.stupidloop + 1 --I'm sick of them constantly turning around for no reason
end
end
end
end)

timer.Create("SpawnLoad",0.1,1,LoadSpawns)
function LoadSpawns()
print("Loading Spawns")

local map = string.gsub(game.GetMap(),"_","q")
local Textfile = string.format("WunceFiles/%sshaidspawns.txt",map )

if !file.Exists( Textfile ) then
print("SPAWN FILE FOR THIS MAP DOES NOT EXIST -> PLEASE ADD SPAWNS TO CREATE A FILE")

else
Spawn = glon.decode( file.Read( Textfile ) )

			for q, lol in pairs(Spawn) do
			i = q
			end
end
end

contentss={}

function SHAID_add_spawn(ply,command,argument) --this worked nicely and without a hitch
if ply:IsAdmin() or ply:IsSuperAdmin() then
local tracedata = {}
tracedata.start = ply:GetShootPos()
tracedata.endpos = ply:GetShootPos() + ( ply:GetAimVector() * 50000 )
tracedata.filter = ply
local trace = util.TraceLine( tracedata )

	if trace.HitWorld then
	i = i + 1
	Spawn[i] = trace.HitPos + Vector(0,0,10)
	
	
	
	local map = string.gsub(game.GetMap(),"_","q")
	local Textfile = string.format("WunceFiles/%sshaidspawns.txt",map )
	
	if file.Exists(Textfile) then
	contentss = glon.decode( file.Read( Textfile ) )
	table.insert(contentss,Spawn[i])
	else
	contentss[1] = Spawn[i]
	end
	
	file.Write( Textfile, glon.encode( contentss ) )
	end
	
end
end
concommand.Add("SHAID_add_spawn",SHAID_add_spawn)

function removecurspawns()
	for q= 1, i do
		Spawn[q] = nil
	end
	i=0
end

function deletefile()
	local map = string.gsub(game.GetMap(),"_","q")
	local Textfile = string.format("WunceFiles/%sspawns.txt",map )
	if file.Exists( Textfile ) then
	file.Delete(Textfile)
	end
end

function SHAID_remove_all_spawns(ply, c, a ) -- I hope this works, its pretty untested
if ply:IsAdmin() or ply:IsSuperAdmin() then
	removecurspawns()
	deletefile()
end
end
concommand.Add("SHAID_remove_all_spawns",SHAID_remove_all_spawns)

function SHAID_Toggle(ply,command,argument)
if SHAID == true and (ply:IsAdmin() or ply:IsSuperAdmin()) then
SHAID = false
npctotalnum = 0
print("SHAID is now disabled")
timer.Destroy("Safety_Net")
elseif SHAID != true and (ply:IsAdmin() or ply:IsSuperAdmin()) then
SHAID = true
BootTime = CurTime()
timer.Create("Safety_Net", 1200 , 0 , function()
print("SAFETY NET RESET HAS BEEN RUN")
zombieamt = 19
antlionamt = 1
remainder = 0
npctotalnum = 0
desiredslaughter = 1
enemyhp = 1
end)
print("SHAID is now enabled")
end
end
concommand.Add("SHAID_Toggle",SHAID_Toggle)

function SHAID_Opt_in(ply,command,argument)
if ply.optin != true then
ply.optin = true
ply.killsection = 0
ply:ChatPrint("You have opted into SHAID")
else
ply.optin = false
ply:ChatPrint("You have opted out SHAID")
end
end
concommand.Add("SHAID_Opt_in",SHAID_Opt_in)


--hook.Add("PlayerDeath", "SHAID_disable", SHAID_disable)

function LocationSpawner(ply)
local possib = {}
local possibnum = 0
for k, spawner in pairs(Spawn) do
	local disty = math.sqrt( math.pow((spawner.x - ply:GetPos().x),2) + math.pow((spawner.y - ply:GetPos().y),2) )
	
	if disty > 2000 and disty < 5000 then -- distances assigned here
		possibnum = possibnum + 1
		possib[possibnum] = spawner
	end
end

if possibnum == 0 then return Spawn[math.random(1,count)]
else return possib[math.random(1,possibnum)]

end
end

function DecideEnemyType()
if GetConVarNumber("SHAID_Zombies_Only") == 1 then return "npc_fastzombie" --these may work now
elseif GetConVarNumber("SHAID_Antlions_Only") == 1 then return "npc_antlion"
else

local enemy = math.random(antlionamt,zombieamt) -- I should get dan's NPC pack so that more enemies can be added

if bossspawn == true then --when I get more boss npcs, add them here
	bossspawn = false
	return "npc_antlionguard"

else
	if enemy == 10 then return "npc_antlion" --replace this with something more appropriate
	elseif enemy < 10 and enemy != 4 and enemy !=5 then return "npc_antlion" 	-- READ THIS: YOU CAN CHANGE MY CODE BUT DON'T FUCKING UPLOAD IT
	elseif (enemy == 4 or enemy == 5) and hcrab then return "npc_headcrab_fast" -- REALLY, DON'T UPLOAD IT. GET SOME SKILL AND MAKE YOUR OWN WORK
	elseif enemy > 10 and enemy != 19 then return "npc_fastzombie"				-- RATHER THAN STEALING THE HARD WORK OF OTHERS ~ Wunce
	elseif enemy == 19 then return "npc_metropolice" 
	else return "npc_poisonzombie"
end
end
end
end

function killsection(NPC, killer, weap)

if table.HasValue(NPCenemies , NPC)  then

if killer:IsPlayer() and killer.optin == true then
killer.killsection = killer.killsection + 1 --I like this part :P
end

if npctotalnum > 0 then
npctotalnum = npctotalnum - 1
end

local itemdrop = math.random(1,20)

if itemdrop == 10 then
local ent = ents.Create("first_aid_spray")
if ent != NULL then
ent:SetPos(NPC:GetPos())
ent:Spawn()
ent:Activate()
timer.Create(string.format("ItemTimer:%s",ent), 20 , 1 , ItemRemove, ent)
end
end

end
end

hook.Add("OnNPCKilled", "killsection", killsection)

function SHAIDstats( ply, text, team )
    if (string.sub(text, 1, 6) == "/SHAID") then
       ply:ChatPrint(string.format("Team Slaughter: %G",slaughter))
	   ply:ChatPrint(string.format("Desired Slaughter: %G",desiredslaughter))
	   ply:ChatPrint(string.format("SHAID NPC's: %i",npctotalnum))
	   local stalkers = 0 
	   for k, NPC in pairs(NPCenemies) do -- I was interested  to see how many were after me and my friend at anyone time
	   if NPC.playertarget == ply then
	   stalkers = stalkers + 1
	   end
	   end
	   ply:ChatPrint(string.format("NPC's chasing you: %i",stalkers))
    end
end
hook.Add( "PlayerSay", "SHAIDstats", SHAIDstats );

function shMenu(ply)
umsg.Start("call_vgui", ply)
umsg.End()
end
concommand.Add("+shmenu",shMenu)

function shMenuclose(ply)
umsg.Start("close_vgui", ply)
umsg.End()
end
concommand.Add("-shmenu",shMenuclose)

function ItemRemove(ent)
if ent != NULL then
ent:Remove()
end
end


function StopTheViolence(NPC)
NPC:AddRelationship("npc_antlion D_LI 99")
NPC:AddRelationship("npc_antlionguard D_LI 99")
NPC:AddRelationship("npc_fastzombie D_LI 99")
NPC:AddRelationship("npc_zombie D_LI 99")
NPC:AddRelationship("npc_headcrab D_LI 99")
NPC:AddRelationship("npc_headcrab_fast D_LI 99")
NPC:AddRelationship("npc_metropolice D_LI 99")
end

